#include <stdio.h>
#include <stdint.h>
#include <openssl/md5.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <stdbool.h>
#include <sys/time.h>

// This Macro control printing md5 result or not
// #define PRINT_MD

#define THREADS_CNT     8
#define REPEATE_TIMES   80000000
#define T_CHECK(x)      {int a = x; if (a != 0) {printf("Unexpected return: %d\n", a); return a;}}

typedef struct segment{
    long start;
    long end;
} seg_t;

static long g_index = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

#ifdef PRINT_MD
static void printf_buf(const char *tag, const uint8_t *buf, int len)
{
    int i;
    printf("%s", tag);
    for (i = 0; i < len; i++) {
        printf("%02X", buf[i]);
    }
    printf("\n");
}
#endif

static char *ltostr(char *str, long val, unsigned base)
{
    ldiv_t r;           /* result of val / base */

    if (base > 36) {    /* no conversion if wrong base */
        *str = '\0';
        return str;
    }

    if (val < 0) {
        *str++ = '-';
    }
    r = ldiv(labs(val), base);

    if (r.quot > 0) {
        str = ltostr (str, r.quot, base);
    }

    *str++ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[(int)r.rem];
    *str   = '\0';

    return str;
}

static void calculate_md5(long index)
{
    char data[32] = {0};
    uint8_t md[16];

    ltostr(data, index, 36);
    MD5((uint8_t *)data, strlen(data), md);
#ifdef PRINT_MD
    printf("MD5(\"%s\"): ", data);
    printf_buf("", md, 16);
#endif
}

static void *task(void *arg)
{
    bool flag = false;
    while (1) {
        pthread_mutex_lock(&mutex);
        flag = g_index >= REPEATE_TIMES;
        pthread_mutex_unlock(&mutex);

        if (flag) {
            break;
        }
        calculate_md5(g_index);

        pthread_mutex_lock(&mutex);
        g_index++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

// Run with single thread
static int test0(void)
{
    int i;

    for (i = 0; i < REPEATE_TIMES; i++) {
        calculate_md5(i);
    }

    return 0;
}

// Run with multiple thread
static int test1(void)
{
    int i;
    pthread_t tids[THREADS_CNT];

    // Create threads
    // TODO: figure out a better way to process failure
    for (i = 0; i < THREADS_CNT; i++) {
        T_CHECK(pthread_create(&tids[i], NULL, task, NULL));
    }

    // Wait for threads
    for (i = 0; i < THREADS_CNT; i++) {
        pthread_join(tids[i], NULL);
    }

    return 0;
}

static double my_clock(void) {
    struct timeval t;
    gettimeofday(&t, NULL);
    return (1.0e-6*t.tv_usec + t.tv_sec);
}

static double time_func(int (*func)(void *), void *args)
{
    double t;
    t = my_clock();
    (*func)(args);

    return my_clock() - t;
}

static void *sub_task(void *arg)
{
    long i;
    seg_t *seg = (seg_t *)arg;

    for (i = seg->start; i <= seg->end; i++) {
        calculate_md5(i);
    }

    return NULL;
}

static int test2(void)
{
    long i;
    long seg_len;
    seg_t seg[THREADS_CNT];
    pthread_t tids[THREADS_CNT];

    seg_len = REPEATE_TIMES / THREADS_CNT;
    for (i = 0; i < THREADS_CNT - 1; i++) {
        seg[i].start = i * seg_len;
        seg[i].end = (i + 1) * seg_len - 1;
    }
    seg[i].start = i * seg_len;
    seg[i].end = REPEATE_TIMES;

    // Create threads
    // TODO: figure out a better way to process failure
    for (i = 0; i < THREADS_CNT; i++) {
        T_CHECK(pthread_create(&tids[i], NULL, sub_task, (void *)&seg[i]));
    }

    // Wait for threads
    for (i = 0; i < THREADS_CNT; i++) {
        pthread_join(tids[i], NULL);
    }

    return 0;

}

int main(int argc, char **argv)
{
    double t;

    t = time_func((void *)test0, NULL);
    printf("1 thread:\t%f seconds\n", t);

    t = time_func((void *)test2, NULL);
    printf("%d thread:\t%f seconds\n", THREADS_CNT, t);

    t = time_func((void *)test1, NULL);
    printf("%d thread:\t%f seconds\n", THREADS_CNT, t);

    return 0;
}
